1 /** 2 * This module implements custom assertions via $(D shouldXXX) functions 3 * that throw exceptions containing information about why the assertion 4 * failed. 5 */ 6 7 module unit_threaded.should; 8 9 import std.traits; // too many to list 10 import std.range; // also 11 12 /** 13 * An exception to signal that a test case has failed. 14 */ 15 class UnitTestException : Exception { 16 this(in string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow { 17 this([msg], file, line, next); 18 } 19 20 this(in string[] msgLines, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow { 21 import std..string : join; 22 23 super(msgLines.join("\n"), next, file, line); 24 this.msgLines = msgLines; 25 } 26 27 override string toString() @safe const pure { 28 import std.algorithm : map; 29 30 return () @trusted{ 31 return msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n"); 32 }(); 33 } 34 35 private: 36 37 const string[] msgLines; 38 39 string getOutputPrefix(in string file, in size_t line) @safe const pure { 40 import std.conv : to; 41 42 return " " ~ file ~ ":" ~ line.to!string ~ " - "; 43 } 44 } 45 46 /** 47 * Verify that the condition is `true`. 48 * Throws: UnitTestException on failure. 49 */ 50 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 51 shouldEqual(cast(bool) condition, true, file, line); 52 } 53 54 /// 55 @safe pure unittest { 56 shouldBeTrue(true); 57 } 58 59 /** 60 * Verify that the condition is `false`. 61 * Throws: UnitTestException on failure. 62 */ 63 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 64 shouldEqual(cast(bool) condition, false, file, line); 65 } 66 67 /// 68 @safe pure unittest { 69 shouldBeFalse(false); 70 } 71 72 /** 73 * Verify that two values are the same. 74 * Floating point values are compared using $(D std.math.approxEqual). 75 * Throws: UnitTestException on failure 76 */ 77 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, 78 in size_t line = __LINE__) { 79 if (!isEqual(value, expected)) { 80 const msg = formatValueInItsOwnLine("Expected: ", expected) ~ formatValueInItsOwnLine(" Got: ", 81 value); 82 throw new UnitTestException(msg, file, line); 83 } 84 } 85 86 /// 87 @safe pure unittest { 88 shouldEqual(true, true); 89 shouldEqual(false, false); 90 shouldEqual(1, 1); 91 shouldEqual("foo", "foo"); 92 shouldEqual([2, 3], [2, 3]); 93 94 shouldEqual(iota(3), [0, 1, 2]); 95 shouldEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]); 96 shouldEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]); 97 shouldEqual([iota(2), iota(3)], [[0, 1], [0, 1, 2]]); 98 99 } 100 101 /** 102 * Verify that two values are not the same. 103 * Throws: UnitTestException on failure 104 */ 105 void shouldNotEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__) { 106 if (isEqual(value, expected)) { 107 const msg = [ 108 "Value:", formatValueInItsOwnLine("", value).join(""), 109 "is not expected to be equal to:", formatValueInItsOwnLine("", expected).join("") 110 ]; 111 throw new UnitTestException(msg, file, line); 112 } 113 } 114 115 /// 116 @safe pure unittest { 117 shouldNotEqual(true, false); 118 shouldNotEqual(1, 2); 119 shouldNotEqual("f", "b"); 120 shouldNotEqual([2, 3], [2, 3, 4]); 121 } 122 123 /// 124 @safe unittest { 125 shouldNotEqual(1.0, 2.0); 126 } 127 128 /** 129 * Verify that the value is null. 130 * Throws: UnitTestException on failure 131 */ 132 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 133 if (value !is null) 134 fail("Value is not null", file, line); 135 } 136 137 /// 138 @safe pure unittest { 139 shouldBeNull(null); 140 } 141 142 /** 143 * Verify that the value is not null. 144 * Throws: UnitTestException on failure 145 */ 146 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 147 if (value is null) 148 fail("Value is null", file, line); 149 } 150 151 /// 152 @safe pure unittest { 153 class Foo { 154 this(int i) { 155 this.i = i; 156 } 157 158 override string toString() const { 159 import std.conv : to; 160 161 return i.to!string; 162 } 163 164 int i; 165 } 166 167 shouldNotBeNull(new Foo(4)); 168 } 169 170 enum isLikeAssociativeArray(T, K) = is(typeof({ 171 if (K.init in T) { 172 } 173 if (K.init !in T) { 174 } 175 })); 176 177 static assert(isLikeAssociativeArray!(string[string], string)); 178 static assert(!isLikeAssociativeArray!(string[string], int)); 179 180 /** 181 * Verify that the value is in the container. 182 * Throws: UnitTestException on failure 183 */ 184 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, 185 in string file = __FILE__, in size_t line = __LINE__) 186 if (isLikeAssociativeArray!(U, T)) { 187 import std.conv : to; 188 189 if (value !in container) { 190 fail(formatValueInItsOwnLine("Value ", 191 value) ~ formatValueInItsOwnLine("not in ", container), file, line); 192 } 193 } 194 195 /// 196 @safe pure unittest { 197 5.shouldBeIn([5 : "foo"]); 198 199 struct AA { 200 int onlyKey; 201 bool opBinaryRight(string op)(in int key) const { 202 return key == onlyKey; 203 } 204 } 205 206 5.shouldBeIn(AA(5)); 207 } 208 209 /** 210 * Verify that the value is in the container. 211 * Throws: UnitTestException on failure 212 */ 213 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, 214 in size_t line = __LINE__) 215 if (!isLikeAssociativeArray!(U, T) && isInputRange!U) { 216 import std.algorithm : find; 217 import std.conv : to; 218 219 if (find(container, value).empty) { 220 fail(formatValueInItsOwnLine("Value ", 221 value) ~ formatValueInItsOwnLine("not in ", container), file, line); 222 } 223 } 224 225 /// 226 @safe pure unittest { 227 shouldBeIn(4, [1, 2, 4]); 228 shouldBeIn("foo", ["foo" : 1]); 229 } 230 231 /** 232 * Verify that the value is not in the container. 233 * Throws: UnitTestException on failure 234 */ 235 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, 236 in string file = __FILE__, in size_t line = __LINE__) 237 if (isLikeAssociativeArray!(U, T)) { 238 import std.conv : to; 239 240 if (value in container) { 241 fail(formatValueInItsOwnLine("Value ", 242 value) ~ formatValueInItsOwnLine("is in ", container), file, line); 243 } 244 } 245 246 /// 247 @safe pure unittest { 248 5.shouldNotBeIn([4 : "foo"]); 249 250 struct AA { 251 int onlyKey; 252 bool opBinaryRight(string op)(in int key) const { 253 return key == onlyKey; 254 } 255 } 256 257 5.shouldNotBeIn(AA(4)); 258 } 259 260 /** 261 * Verify that the value is not in the container. 262 * Throws: UnitTestException on failure 263 */ 264 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, 265 in size_t line = __LINE__) 266 if (!isLikeAssociativeArray!(U, T) && isInputRange!U) { 267 import std.algorithm : find; 268 import std.conv : to; 269 270 if (!find(container, value).empty) { 271 fail(formatValueInItsOwnLine("Value ", 272 value) ~ formatValueInItsOwnLine("is in ", container), file, line); 273 } 274 } 275 276 /// 277 @safe unittest { 278 auto arrayRangeWithoutLength(T)(T[] array) { 279 struct ArrayRangeWithoutLength(T) { 280 private: 281 T[] array; 282 public: 283 T front() const @property { 284 return array[0]; 285 } 286 287 void popFront() { 288 array = array[1 .. $]; 289 } 290 291 bool empty() const @property { 292 return array.empty; 293 } 294 } 295 296 return ArrayRangeWithoutLength!T(array); 297 } 298 299 shouldNotBeIn(3.5, [1.1, 2.2, 4.4]); 300 shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]); 301 shouldNotBeIn(1, arrayRangeWithoutLength([2, 3, 4])); 302 } 303 304 /** 305 * Verify that expr throws the templated Exception class. 306 * This succeeds if the expression throws a child class of 307 * the template parameter. 308 * Returns: The caught throwable. 309 * Throws: UnitTestException on failure (when expr does not 310 * throw the expected exception) 311 */ 312 auto shouldThrow(T : Throwable = Exception, E)(lazy E expr, 313 in string file = __FILE__, in size_t line = __LINE__) { 314 import std.conv : text; 315 316 return () @trusted{ // @trusted because of catching Throwable 317 try { 318 const result = threw!T(expr); 319 if (result) 320 return result.throwable; 321 } catch (Throwable t) 322 fail(text("Expression threw ", typeid(t), 323 " instead of the expected ", T.stringof, ":\n", t.msg), file, line); 324 325 fail("Expression did not throw", file, line); 326 assert(0); 327 }(); 328 } 329 330 /// 331 @safe pure unittest { 332 void funcThrows(string msg) { 333 throw new Exception(msg); 334 } 335 336 try { 337 auto exception = funcThrows("foo bar").shouldThrow; 338 assert(exception.msg == "foo bar"); 339 } catch (Exception e) { 340 assert(false, "should not have thrown anything and threw: " ~ e.msg); 341 } 342 } 343 344 /// 345 @safe pure unittest { 346 void func() { 347 } 348 349 try { 350 func.shouldThrow; 351 assert(false, "Should never get here"); 352 } catch (Exception e) 353 assert(e.msg == "Expression did not throw"); 354 } 355 356 /// 357 @safe pure unittest { 358 void funcAsserts() { 359 assert(false, "Oh noes"); 360 } 361 362 try { 363 funcAsserts.shouldThrow; 364 assert(false, "Should never get here"); 365 } catch (Exception e) 366 assert( 367 e.msg 368 == "Expression threw core.exception.AssertError instead of the expected Exception:\nOh noes"); 369 } 370 371 /** 372 * Verify that expr throws the templated Exception class. 373 * This only succeeds if the expression throws an exception of 374 * the exact type of the template parameter. 375 * Returns: The caught throwable. 376 * Throws: UnitTestException on failure (when expr does not 377 * throw the expected exception) 378 */ 379 auto shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr, 380 in string file = __FILE__, in size_t line = __LINE__) { 381 import std.conv : text; 382 383 const threw = threw!T(expr); 384 if (!threw) 385 fail("Expression did not throw", file, line); 386 387 //Object.opEquals is @system and impure 388 const sameType = () @trusted{ return threw.typeInfo == typeid(T); }(); 389 if (!sameType) 390 fail(text("Expression threw wrong type ", threw.typeInfo, 391 "instead of expected type ", typeid(T)), file, line); 392 393 return threw.throwable; 394 } 395 396 /** 397 * Verify that expr does not throw the templated Exception class. 398 * Throws: UnitTestException on failure 399 */ 400 void shouldNotThrow(T : Throwable = Exception, E)(lazy E expr, 401 in string file = __FILE__, in size_t line = __LINE__) { 402 if (threw!T(expr)) 403 fail("Expression threw", file, line); 404 } 405 406 /** 407 * Verify that an exception is thrown with the right message 408 */ 409 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, string msg, 410 string file = __FILE__, size_t line = __LINE__) { 411 auto threw = threw!T(expr); 412 if (!threw) 413 fail("Expression did not throw", file, line); 414 415 threw.throwable.msg.shouldEqual(msg, file, line); 416 } 417 418 /// 419 @safe pure unittest { 420 void funcThrows(string msg) { 421 throw new Exception(msg); 422 } 423 424 funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar"); 425 funcThrows("foo bar").shouldThrowWithMessage("foo bar"); 426 } 427 428 //@trusted because the user might want to catch a throwable 429 //that's not derived from Exception, such as RangeError 430 private auto threw(T : Throwable, E)(lazy E expr) @trusted { 431 432 static struct ThrowResult { 433 bool threw; 434 TypeInfo typeInfo; 435 immutable(T) throwable; 436 437 T opCast(T)() @safe @nogc const pure if (is(T == bool)) { 438 return threw; 439 } 440 } 441 442 import std.stdio; 443 444 try { 445 expr(); 446 } catch (T e) { 447 return ThrowResult(true, typeid(e), cast(immutable) e); 448 } 449 450 return ThrowResult(false); 451 } 452 453 void fail(in string output, in string file, in size_t line) @safe pure { 454 throw new UnitTestException([output], file, line); 455 } 456 457 void fail(in string[] lines, in string file, in size_t line) @safe pure { 458 throw new UnitTestException(lines, file, line); 459 } 460 461 // Formats output in different lines 462 private string[] formatValueInItsOwnLine(T)(in string prefix, auto ref T value) { 463 464 import std.conv : to; 465 466 static if (isSomeString!T) { 467 // isSomeString is true for wstring and dstring, 468 // so call .to!string anyway 469 return [prefix ~ `"` ~ value.to!string ~ `"`]; 470 } else static if (isInputRange!T) { 471 return formatRange(prefix, value); 472 } else { 473 return [prefix ~ convertToString(value)]; 474 } 475 } 476 477 // helper function for non-copyable types 478 string convertToString(T)(in auto ref T value) { // std.conv.to sometimes is @system 479 import std.conv : to; 480 import std.traits : Unqual; 481 482 static if (__traits(compiles, value.to!string)) 483 return () @trusted{ return value.to!string; }(); 484 else static if (__traits(compiles, value.toString)) { 485 static if (isObject!T) 486 return () @trusted{ return (cast(Unqual!T) value).toString; }(); 487 else 488 return value.toString; 489 } else 490 return T.stringof ~ "<cannot print>"; 491 } 492 493 private string[] formatRange(T)(in string prefix, T value) { 494 import std.conv : to; 495 import std.range : ElementType; 496 import std.algorithm : map, reduce, max; 497 498 //some versions of `to` are @system 499 auto defaultLines = () @trusted{ return [prefix ~ value.to!string]; }(); 500 501 static if (!isInputRange!(ElementType!T)) 502 return defaultLines; 503 else { 504 import std.array : array; 505 506 const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max; 507 const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) 508 || maxElementSize > 10; 509 if (!tooBigForOneLine) 510 return defaultLines; 511 return [prefix ~ "["] ~ value.map!(a => formatValueInItsOwnLine(" ", 512 a).join("") ~ ",").array ~ " ]"; 513 } 514 } 515 516 private enum isObject(T) = is(T == class) || is(T == interface); 517 518 bool isEqual(V, E)(in auto ref V value, in auto ref E expected) 519 if (!isObject!V && (!isInputRange!V || !isInputRange!E) 520 && !isFloatingPoint!V && !isFloatingPoint!E && is(typeof(value == expected) == bool)) { 521 return value == expected; 522 } 523 524 bool isEqual(V, E)(in V value, in E expected) 525 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) 526 && is(typeof(value == expected) == bool)) { 527 return value == expected; 528 } 529 530 bool isApproxEqual(V, E)(in V value, in E expected) 531 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) 532 && is(typeof(value == expected) == bool)) { 533 import std.math; 534 535 return approxEqual(value, expected); 536 } 537 538 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, 539 size_t line = __LINE__) 540 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) 541 && is(typeof(value == expected) == bool)) { 542 if (!isApproxEqual(value, expected)) { 543 const msg = formatValueInItsOwnLine("Expected approx: ", expected) 544 ~ formatValueInItsOwnLine(" Got : ", value); 545 throw new UnitTestException(msg, file, line); 546 } 547 } 548 549 /// 550 @safe unittest { 551 1.0.shouldApproxEqual(1.0001); 552 } 553 554 bool isEqual(V, E)(V value, E expected) 555 if (!isObject!V && isInputRange!V && isInputRange!E 556 && !isSomeString!V && is(typeof(isEqual(value.front, expected.front)))) { 557 558 while (!value.empty && !expected.empty) { 559 if (!isEqual(value.front, expected.front)) 560 return false; 561 value.popFront; 562 expected.popFront; 563 } 564 565 return value.empty && expected.empty; 566 } 567 568 bool isEqual(V, E)(V value, E expected) 569 if (!isObject!V && isInputRange!V && isInputRange!E 570 && isSomeString!V && isSomeString!E && is(typeof(isEqual(value.front, expected.front)))) { 571 if (value.length != expected.length) 572 return false; 573 // prevent auto-decoding 574 foreach (i; 0 .. value.length) 575 if (value[i] != expected[i]) 576 return false; 577 578 return true; 579 } 580 581 template IsField(A...) if (A.length == 1) { 582 enum IsField = __traits(compiles, A[0].init); 583 } 584 585 bool isEqual(V, E)(V value, E expected) if (isObject!V && isObject!E) { 586 import std.meta : staticMap, Filter; 587 588 static assert(is(typeof(() { 589 string s1 = value.toString; 590 string s2 = expected.toString; 591 })), "Cannot compare instances of " ~ V.stringof ~ " or " 592 ~ E.stringof ~ " unless toString is overridden for both"); 593 594 if (value is null && expected !is null) 595 return false; 596 if (value !is null && expected is null) 597 return false; 598 if (value is null && expected is null) 599 return true; 600 601 template IsFieldOf(T, string s) { 602 static if (__traits(compiles, IsField!(typeof(__traits(getMember, T.init, s))))) 603 enum IsFieldOf = IsField!(typeof(__traits(getMember, T.init, s))); 604 else 605 enum IsFieldOf = false; 606 } 607 608 auto members(T)(T obj) { 609 import std.typecons : Tuple; 610 611 alias Member(string name) = typeof(__traits(getMember, T, name)); 612 alias IsFieldOfT(string s) = IsFieldOf!(T, s); 613 alias FieldNames = Filter!(IsFieldOfT, __traits(allMembers, T)); 614 alias FieldTypes = staticMap!(Member, FieldNames); 615 616 Tuple!FieldTypes ret; 617 foreach (i, name; FieldNames) 618 ret[i] = __traits(getMember, obj, name); 619 620 return ret; 621 } 622 623 static if (is(V == interface)) 624 return false; 625 else 626 return members(value) == members(expected); 627 } 628 629 /** 630 * Verify that rng is empty. 631 * Throws: UnitTestException on failure. 632 */ 633 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) 634 if (isInputRange!R) { 635 import std.conv : text; 636 637 if (!rng.empty) 638 fail(text("Range not empty: ", rng), file, line); 639 } 640 641 /** 642 * Verify that rng is empty. 643 * Throws: UnitTestException on failure. 644 */ 645 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__) 646 if (isInputRange!R) { 647 import std.conv : text; 648 649 if (!rng.empty) 650 fail(text("Range not empty: ", rng), file, line); 651 } 652 653 /** 654 * Verify that aa is empty. 655 * Throws: UnitTestException on failure. 656 */ 657 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__) 658 if (isAssociativeArray!T) { 659 //keys is @system 660 () @trusted{ 661 if (!aa.keys.empty) 662 fail("AA not empty", file, line); 663 }(); 664 } 665 666 /// 667 @safe pure unittest { 668 int[] ints; 669 string[] strings; 670 string[string] aa; 671 672 shouldBeEmpty(ints); 673 shouldBeEmpty(strings); 674 shouldBeEmpty(aa); 675 676 ints ~= 1; 677 strings ~= "foo"; 678 aa["foo"] = "bar"; 679 } 680 681 /** 682 * Verify that rng is not empty. 683 * Throws: UnitTestException on failure. 684 */ 685 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__) 686 if (isInputRange!R) { 687 if (rng.empty) 688 fail("Range empty", file, line); 689 } 690 691 /** 692 * Verify that aa is not empty. 693 * Throws: UnitTestException on failure. 694 */ 695 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__) 696 if (isAssociativeArray!T) { 697 //keys is @system 698 () @trusted{ 699 if (aa.keys.empty) 700 fail("AA empty", file, line); 701 }(); 702 } 703 704 /// 705 @safe pure unittest { 706 int[] ints; 707 string[] strings; 708 string[string] aa; 709 710 ints ~= 1; 711 strings ~= "foo"; 712 aa["foo"] = "bar"; 713 714 shouldNotBeEmpty(ints); 715 shouldNotBeEmpty(strings); 716 shouldNotBeEmpty(aa); 717 } 718 719 /** 720 * Verify that t is greater than u. 721 * Throws: UnitTestException on failure. 722 */ 723 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 724 in string file = __FILE__, in size_t line = __LINE__) { 725 import std.conv : text; 726 727 if (t <= u) 728 fail(text(t, " is not > ", u), file, line); 729 } 730 731 /// 732 @safe pure unittest { 733 shouldBeGreaterThan(7, 5); 734 } 735 736 /** 737 * Verify that t is smaller than u. 738 * Throws: UnitTestException on failure. 739 */ 740 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 741 in string file = __FILE__, in size_t line = __LINE__) { 742 import std.conv : text; 743 744 if (t >= u) 745 fail(text(t, " is not < ", u), file, line); 746 } 747 748 /// 749 @safe pure unittest { 750 shouldBeSmallerThan(5, 7); 751 } 752 753 /** 754 * Verify that t and u represent the same set (ordering is not important). 755 * Throws: UnitTestException on failure. 756 */ 757 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, 758 in string file = __FILE__, in size_t line = __LINE__) 759 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) { 760 if (!isSameSet(value, expected)) { 761 const msg = formatValueInItsOwnLine("Expected: ", expected) ~ formatValueInItsOwnLine(" Got: ", 762 value); 763 throw new UnitTestException(msg, file, line); 764 } 765 } 766 767 /// 768 @safe pure unittest { 769 import std.range : iota; 770 771 auto inOrder = iota(4); 772 auto noOrder = [2, 3, 0, 1]; 773 auto oops = [2, 3, 4, 5]; 774 775 inOrder.shouldBeSameSetAs(noOrder); 776 inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException; 777 778 struct Struct { 779 int i; 780 } 781 782 [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]); 783 } 784 785 private bool isSameSet(T, U)(auto ref T t, auto ref U u) { 786 import std.algorithm : canFind; 787 788 //sort makes the element types have to implement opCmp 789 //instead, try one by one 790 auto ta = t.array; 791 auto ua = u.array; 792 if (ta.length != ua.length) 793 return false; 794 foreach (element; ta) { 795 if (!ua.canFind(element)) 796 return false; 797 } 798 799 return true; 800 } 801 802 /** 803 * Verify that value and expected do not represent the same set (ordering is not important). 804 * Throws: UnitTestException on failure. 805 */ 806 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, 807 in string file = __FILE__, in size_t line = __LINE__) 808 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) { 809 if (isSameSet(value, expected)) { 810 const msg = [ 811 "Value:", formatValueInItsOwnLine("", value).join(""), 812 "is not expected to be equal to:", formatValueInItsOwnLine("", expected).join("") 813 ]; 814 throw new UnitTestException(msg, file, line); 815 } 816 } 817 818 /// 819 @safe pure unittest { 820 auto inOrder = iota(4); 821 auto noOrder = [2, 3, 0, 1]; 822 auto oops = [2, 3, 4, 5]; 823 824 inOrder.shouldNotBeSameSetAs(oops); 825 inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException; 826 } 827 828 /** 829 If two strings represent the same JSON regardless of formatting 830 */ 831 void shouldBeSameJsonAs(in string actual, in string expected, 832 in string file = __FILE__, in size_t line = __LINE__) @trusted // not @safe pure due to parseJSON 833 { 834 import std.json : parseJSON, JSONException; 835 836 auto parse(in string str) { 837 try 838 return str.parseJSON; 839 catch (JSONException ex) 840 throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line); 841 } 842 843 parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line); 844 } 845 846 /// 847 @safe unittest { // not pure because parseJSON isn't pure 848 `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`); 849 `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`); 850 `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException; 851 try 852 `oops`.shouldBeSameJsonAs(`oops`); 853 catch (Exception e) 854 assert(e.msg == "Error parsing JSON: Unexpected character 'o'. (Line 1:1)"); 855 } 856 857 auto should(E)(lazy E expr) { 858 859 struct ShouldNot { 860 861 bool opEquals(U)(auto ref U other, in string file = __FILE__, in size_t line = __LINE__) { 862 expr.shouldNotEqual(other, file, line); 863 return true; 864 } 865 866 void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const 867 if (op == "in") { 868 shouldNotBeIn(expr, range, file, line); 869 } 870 871 void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const 872 if (op == "~" && isInputRange!R) { 873 shouldThrow!UnitTestException(shouldBeSameSetAs(expr, range), file, line); 874 } 875 876 void opBinary(string op, E)(in E expected, string file = __FILE__, size_t line = __LINE__) 877 if (isFloatingPoint!E) { 878 shouldThrow!UnitTestException(shouldApproxEqual(expr, expected), file, line); 879 } 880 881 // void opDispatch(string s, A...)(auto ref A args) 882 // { 883 // import std.functional: forward; 884 // mixin(`Should().` ~ string ~ `(forward!args)`); 885 // } 886 } 887 888 struct Should { 889 890 bool opEquals(U)(auto ref U other, in string file = __FILE__, in size_t line = __LINE__) { 891 expr.shouldEqual(other, file, line); 892 return true; 893 } 894 895 void throw_(T : Throwable = Exception)(in string file = __FILE__, in size_t line = __LINE__) { 896 shouldThrow!T(expr, file, line); 897 } 898 899 void throwExactly(T : Throwable = Exception)(in string file = __FILE__, 900 in size_t line = __LINE__) { 901 shouldThrowExactly!T(expr, file, line); 902 } 903 904 void throwWithMessage(T : Throwable = Exception)(in string file = __FILE__, 905 in size_t line = __LINE__) { 906 shouldThrowWithMessage!T(expr, file, line); 907 } 908 909 void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const 910 if (op == "in") { 911 shouldBeIn(expr, range, file, line); 912 } 913 914 void opBinary(string op, R)(R range, in string file = __FILE__, in size_t line = __LINE__) const 915 if (op == "~" && isInputRange!R) { 916 shouldBeSameSetAs(expr, range, file, line); 917 } 918 919 void opBinary(string op, E)(in E expected, string file = __FILE__, size_t line = __LINE__) 920 if (isFloatingPoint!E) { 921 shouldApproxEqual(expr, expected, file, line); 922 } 923 924 auto not() { 925 return ShouldNot(); 926 } 927 } 928 929 return Should(); 930 } 931 932 /// 933 @safe pure unittest { 934 1.should == 1; 935 1.should.not == 2; 936 1.should in [1, 2, 3]; 937 4.should.not in [1, 2, 3]; 938 939 void funcThrows() { 940 throw new Exception("oops"); 941 } 942 943 funcThrows.should.throw_; 944 } 945 946 T be(T)(T sh) { 947 return sh; 948 } 949 950 /// 951 @safe pure unittest { 952 1.should.be == 1; 953 1.should.not.be == 2; 954 1.should.be in [1, 2, 3]; 955 4.should.not.be in [1, 2, 3]; 956 }